# -*- mode: python; coding: utf-8; -*-
##--------------------------------------------------------------------------##
##
## Copyright (C) 1998-2003 Markus Oberhumer <markus@oberhumer.com>
## Copyright (C) 2003 Mt. Hood Playing Card Co.
## Copyright (C) 2005-2010 Skomoroh <skomoroh@users.sourceforge.net>
## Copyright (C) 2010-2013 Jeremy Austin-Bardo <tjaustinbardo@gmail.com>
##
## This program is free software: you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation, version 3 of the License.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program.  If not, see <http://www.gnu.org/licenses/>.
##
##--------------------------------------------------------------------------##
"""
atomic moves (undo / redo)
==========================
Currently we have the following atomic moves:
  - move the top cards from one stack on the top of another
  - flip the top card of a stack
  - turn a whole stack onto another stack
  - update the model or complete view a stack
  - increase the round (the number of redeals)
  - save the seed of game.random
  - shuffle a stack
"""
__docformat__ = "restructuredtext en"

from .. import animation
from ..settings import CONF_OPT

class AtomicMove(object):
    """
    Abstract atomic move
    """
    __slots__ = []

    def do(self, game):
        self.redo(game)

#    def __repr__(self):
#        return str(self.__class__.__dict__)

#    def __str__(self):
#        return str(self.__class__.__dict__)

    def cmpForRedo(self, other):
        """
        Custom comparision for detecting redo moves.

        :seealso: GameMove.log() for useage.
        """
        return (self.__class__ is other.__class__ and
                all(getattr(self, attr) is getattr(other, attr)
                    for attr in self.__slots__))

    def __cmp__(self, other):
        return self.cmpForRedo(other)


class AMoveMove(AtomicMove):
    """
    Play the selected top num cards from a stack to build on another stack.
    """
    __slots__ = ['ncards', 'from_stack_id', 'to_stack_id', 'frames', 'shadow']

    def __init__(self, ncards, from_stack, to_stack, frames=-1, shadow=-1):
        """
        __init__(ncards, from_stack, to_stack[, frames=-1, shadows=-1])
          -> None

        :Parameters:
          ncards : int
            Number of cards from top of pile to move.
          from_stack : Stack
            Pile cards being played.
          to_stack : Stack
            Pile cards being built.

        :Keywords:
          frames : int
            Number of animation frames. Default is 8 frames.
          shadows : int
            Draw shadow under cards being played. Default is 'shadow' option.

        :IVariables:
          ncards : int
            Number of cards from top of pile to move.
          from_stack_id : int
            Id of pile cards being played.
          to_stack_id : int
            Id of pile cards being built.
          frames : int
            Number of animation frames. Default is 8 frames.
          shadows : int
            Draw shadow under cards being played. Default is 'shadow' option.
        """
        assert from_stack and to_stack and from_stack is not to_stack
        assert 0 < ncards <= len(from_stack.cards)
        self.ncards = ncards
        self.from_stack_id = from_stack.id
        self.to_stack_id = to_stack.id
        self.frames = frames
        self.shadow = shadow

    def _doMove(self, game, ncards, from_stack, to_stack):
        """
        _doMove(game, from_stack, to_stack) -> None

        Perform the actual movement.

        :Parameters:
          game : Game
            Current game object.
          ncards : int
            Number of cards from top of pile to move.
          from_stack : Stack
            Pile cards being played.
          to_stack : Stack
            Pile cards being built.
        """
        move = game._move
        if move['state'] == game.S_PLAY:
            assert to_stack.cardsBuild(from_stack, from_stack.cards[-ncards:])
        frames = self.frames
        if frames == -2 and move['state'] not in (game.S_UNDO, game.S_REDO):
            frames = 0  # don't use animation for drag-move
        cards = from_stack.cards[-ncards:]
        if frames != 0:
            from_stack.unshadeStack()
            x, y = to_stack.getPosition()
            animation.animate_play(game, from_stack, to_stack, cards, x, y,
                                   frames=frames, shadow=self.shadow)
        for i in range(ncards):
            from_stack.removeCard()
        for c in cards:
            to_stack.addCard(c)
        from_stack.updatePositions()
        to_stack.updatePositions()

    def redo(self, game):
        """
        redo(game) -> None

        Perform movement from from_stack to to_stack.

        :Parameters:
          game : Game
            Current game object.
        """
        self._doMove(game, self.ncards, game.allstacks[self.from_stack_id],
                     game.allstacks[self.to_stack_id])

    def undo(self, game):
        """
        undo(game) -> None

        Perform movement from to_stack to from_stack.

        :Parameters:
          game : Game
            Current game object.
        """
        self._doMove(game, self.ncards, game.allstacks[self.to_stack_id],
                     game.allstacks[self.from_stack_id])

#    def cmpForRedo(self, other):
#        return (self.__class__ is other.__class__ and
#                all(getattr(self, attr) is getattr(other, attr)
#                    for attr in self.__slots__))

class AFlipMove(AtomicMove):
    """
    Flip the top card over on stack with animation
    """
    __slots__ = ['stack_id', 'frames', 'shadow']

    def __init__(self, stack, frames=-1, shadow=-1):
        self.stack_id = stack.id
        self.frames = frames
        self.shadow = shadow

    def _doMove(self, game, stack):
        """
        __init__(stack) -> None

        :Parameters:
          game : Game
            Current game object.
          stack : Stack
            Pile top card being flipped.

        :IVariables:
          stack_id : int
            Id of pile top card being flipped.
        """
        if CONF_OPT['animate_flip'] and self.frames != 0:
            animation.animate_flip_and_play(game, stack)

        card = stack.TOPCARD
        if card.face_up:
            card.show_back()
        else:
            card.show_face()

    def redo(self, game):
        """
        redo(game) -> None

        Perform flip movement of stack.

        :Parameters:
          game : Game
            Current game object.
        """
        self._doMove(game, game.allstacks[self.stack_id])

    def undo(self, game):
        """
        undo(game) -> None

        Perform flip movement of stack.

        :Parameters:
          game : Game
            Current game object.
        """
        self._doMove(game, game.allstacks[self.stack_id])


class AFlipAndMoveMove(AtomicMove):
    """
    Flip and play the top card from_stack to build on to_stack.
    """
    __slots__ =['from_stack_id', 'to_stack_id', 'frames', 'shadow']

    def __init__(self, from_stack, to_stack, frames, shadow):
        """
        __init__(from_stack, to_stack[, frames=-1, shadow=-1]) -> None

        :Parameters:
          from_stack : Stack
            Pile cards being played.
          to_stack : Stack
            Pile cards being built.

        :Keywords:
          frames : int
            Number of animation frames. Default is 'animate_speed' option.

        :IVariables:
          from_stack_id : int
            Id of pile cards being played.
          to_stack_id : int
            Id of pile cards being built.
          frames : int
            Number of animation frames.
        """
        assert from_stack is not None and to_stack is not None
        assert from_stack is not to_stack
        self.from_stack_id = from_stack.id
        self.to_stack_id = to_stack.id
        self.frames = frames
        self.shadow = shadow

    def _doMove(self, game, from_stack, to_stack):
        """
        _doMove(from_stack, to_stack) -> None

        :Parameters:
          game : Game
            Current game object.
          from_stack : Stack
            Pile cards being played.
          to_stack : Stack
            Pile cards being built.
        """
        if game.move_state == game.S_PLAY:
            assert to_stack.cardsBuild(from_stack, from_stack.TOPCARD)
        if self.frames == 0 or not CONF_OPT:
            moved = True
        else:
            moved = animation.animate_flip_and_play(game, from_stack,
                                                    to_stack, self.frames)
        card = from_stack.TOPCARD
        if card.face_up:
            card.show_back()
        else:
            card.show_face()
        if not moved:
            cards = from_stack.cards[-1:]
            x, y = to_stack.getPosition()
            animation.animate_play(game, from_stack, to_stack, cards, x, y,
                                   frames=self.frames, shadow=0)
        card = from_stack.removeCard(update=False)
        to_stack.addCard(card, update=False)
        from_stack._update_text()
        to_stack._update_text()

    def redo(self, game):
        """
        redo(game) -> None

        Perform movement to stack.

        :Parameters:
          game : Game
            Current game object.
        """
        self._doMove(game, game.allstacks[self.from_stack_id],
                     game.allstacks[self.to_stack_id])

    def undo(self, game):
        """
        undo(game) -> None

        Perform movement to stack.

        :Parameters:
          game : Game
            Current game object.
        """
        self._doMove(game, game.allstacks[self.to_stack_id],
                     game.allstacks[self.from_stack_id])


class AFlipAllMove(AtomicMove):
    """
    Flip all cards of a pile.
    """
    __slots__ = ['stack_id', 'frames', 'shadow']

    def __init__(self, stack, frames=-1, shadow=-1):
        """
        __init__(stack) -> None

        :Parameters:
          stack : Stack
            Pile to flip all cards.

        :IVariables:
          stack_id : Stack
            Id of pile to flip all cards.
        """
        assert stack
        self.stack_id = stack.id
        self.frames = frames
        self.shadow = shadow

    def _doMove(self, stack):
        """
        _doMove(game) -> None

        Perform the actual movement.

        :Parameters:
          stack : Stack
            Pile to flip all cards.
        """
        for card in stack.cards:
            if card.face_up:
                card.show_back()
            else:
                card.show_face()
        stack.refreshView()

    def redo(self, game):
        """
        redo(game) -> None

        Perform flip all cards on stack.

        :Parameters:
          game : Game
            Current game object.
        """
        self._doMove(game, game.allstacks[self.stack_id])

    def undo(self, game):
        """
        undo(game) -> None

        Perform flip all cards on stack.

        :Parameters:
          game : Game
            Current game object.
        """
        self._doMove(game, game.allstacks[self.stack_id])


class ATurnStackMove(AtomicMove):
    """
    Turn over face-up from_stack onto empty to_stack.
    """
    __slots__ = ['from_stack_id', 'to_stack_id', 'frames', 'shadow']

    def __init__(self, from_stack, to_stack, frames=-1, shadow=-1):
        """
        __init__(from_stack, to_stack) -> None

        :Parameters:
          from_stack : Stack
            Pile cards being played.
          to_stack : Stack
            Pile cards being built.

        :IVariables:
          from_stack_id : int
            Id of pile cards being played.
          to_stack_id : int
            Id of pile cards being built.
        """
        assert from_stack is not None and to_stack is not None
        assert from_stack is not to_stack
        self.from_stack_id = from_stack.id
        self.to_stack_id = to_stack.id
        self.frames = frames
        self.shadow = shadow

    def _doMove(self, game, from_stack, to_stack, redo=True):
        """
        _doMove(from_stack, to_stack[, redo=True]) -> None

        :Parameters:
          from_stack : Stack
            Pile cards being played.
          to_stack : Stack
            Pile cards being built.

        :Keywords:
          redo : bool
            Movement is not being undone.
        """
        assert len(from_stack.cards) > 0
        assert len(to_stack.cards) == 0
        l = len(from_stack.cards)
        for i in range(l):
            card = from_stack.removeCard(unhide=True, update=False)
            if redo:
                assert card.face_up
                card.show_back(unhide=True)
            else:
                assert not card.face_up
                card.show_face(unhide=True)
            to_stack.addCard(card, unhide=True, update=False)
        from_stack._update_text()
        to_stack._update_text()

    def redo(self, game):
        """
        redo(game) -> None

        Perform movement to stacks.

        :Parameters:
          game : Game
            Current game object.
        """
        self._doMove(game, game.allstacks[self.from_stack_id],
                     game.allstacks[self.to_stack_id], redo=True)

    def undo(self, game):
        """
        undo(game) -> None

        Perform movement to stacks.

        :Parameters:
          game : Game
            Current game object.
        """
        self._doMove(game, game.allstacks[self.to_stack_id],
                     game.allstacks[self.from_stack_id], redo=False)


class AUpdateStackMove(AtomicMove):
    """
    Update the attr of a pile. Only needed for complex games in combination
    with undo.
    """
    __slots__ = ['stack_id', 'flags']

    def __init__(self, stack, flags):
        """
        __init__(stack) -> None

        :Parameters:
          stack : Stack
            Pile to be updated.
          flags : int
            Update mode.

        :IVariables:
          stack_id : Stack
            Id of pile with attr to update.
          flags : int
            Update mode.
        """
        assert stack
        self.stack_id = stack.id
        self.flags = flags

    def _doMove(self, game, stack, undo):
        """
        _doMove(from_stack, to_stack[, redo=True]) -> None

        :Parameters:
          stack : Stack
            Pile  with bieng attr modified.
          undo : int
            current undo state.
        """
        if self.flags & 64:
            stack.updateModel(undo, self.flags)
        else:
            if self.flags & 16:
                stack._update_text()
            if self.flags & 32:
                stack.refreshView()

    def redo(self, game):
        """
        redo(game) -> None

        Update stack attr.

        :Parameters:
          game : Game
            Current game object.
        """
        if (self.flags & 3) in (1, 3):
            self._doMove(game, game.allstacks[self.stack_id], 0)

    def undo(self, game):
        """
        undo(game) -> None

        Restore stack attr.

        :Parameters:
          game : Game
            Current game object.
        """
        if (self.flags & 3) in (2, 3):
            self._doMove(game, game.allstacks[self.stack_id], 1)

#    def cmpForRedo(self, other):
#        return (self.__class__ is other.__class__ and
#                all(getattr(self, attr) is getattr(other, attr)
#                    for attr in self.__slots__))

#AUpdateStackModelMove = AUpdateStackMove
#AUpdateStackViewMove = AUpdateStackMove

class ANextRoundMove(AtomicMove):
    """
    Change the round attr of a stack.
    """
    __slots__ = ['stack_id']

    def __init__(self, stack):
        """
        __init__(stack) -> None

        :Parameters:
          stack : Stack
            Pile with round attr to change.

        :IVariables:
          stack_id : Stack
            Id of pile with round attr to change.
        """
        assert stack
        self.stack_id = stack.id

    def _doMove(self, game, stack, redo=True):
        """
        _doMove(game, stack[, redo=True]) -> None

        Perform the actual movement.

        :Parameters:
          game : Game
            Current game object.

          stack : Stack
            Pile with round attr being changed.

        :Keywords:
          redo : bool
            Movement is not being undone.
        """
        assert stack is game.s['talon']
        if redo:
            assert stack.round < stack.max_rounds or stack.max_rounds < 0
            stack.round += 1
        else:
            assert stack.round > 1
            stack.round -= 1
        stack._update_text()

    def redo(self, game):
        """
        redo(game) -> None

        Perform round attr increase on stack.

        :Parameters:
          game : Game
            Current game object.
        """
        self._doMove(game, game.allstacks[self.stack_id])

    def undo(self, game):
        """
        undo(game) -> None

        Perform round attr decrease of stack.

        :Parameters:
          game : Game
            Current game object.
        """
        self._doMove(game, game.allstacks[self.stack_id])

#    def cmpForRedo(self, other):
#        return (self.__class__ is other.__class__ and
#                all(getattr(self, attr) is getattr(other, attr)
#                    for attr in self.__slots__))

class ASaveSeedMove(AtomicMove):
    """
    Save the current random state. (Needed for undo in some games)
    """
    __slots__ = ['state']

    def __init__(self, game):
        """
        __init__(game) -> None

        :Parameters:
          state : int
            Current random state.

        :IVariables:
          state : int
            Set the current random state.
        """
        self.state = game.random.getstate()

    def redo(self, game):
        """
        redo(game) -> None

        Set the current random state.

        :Parameters:
          game : Game
            Current game object.
        """
        game.random.setstate(self.state)

    def undo(self, game):
        """
        redo(game) -> None

        Set the current random state.

        :Parameters:
          game : Game
            Current game object.
        """
        game.random.setstate(self.state)

#    def cmpForRedo(self, other):
#        return (self.__class__ is other.__class__ and
#                all(getattr(self, attr) is getattr(other, attr)
#                    for attr in self.__slots__))

#class ASaveStateMove(AtomicMove):
#    """
#    Save game variables
#    """
#    __slots__ = ['state', 'flags']

#    def __init__(self, state, flags):
#        """
#        __init__(flags) -> None

#        Update the state of a game. Only needed for complex games in
#        combination with undo.

#        :Parameters:
#          state : int
#            Current game state.

#          flags : int
#            Update mode.
#        """
#        self.state = state
#        self.flags = flags

#    def redo(self, game):
#        """
#        redo(game) -> None

#        Set game set.

#        :Parameters:
#          game : Game
#            Current game object.
#        """
#        if (self.flags & 3) in (1, 3):
#            game.state = self.state

#    def undo(self, game):
#        """
#        undo(game) -> None

#        Restore game set.

#        :Parameters:
#          game : Game
#            Current game object.
#        """
#        if (self.flags & 3) in (2, 3):
#            game.state = self.state

#    def cmpForRedo(self, other):
#        return (self.__class__ is other.__class__ and
#                all(getattr(self, attr) is getattr(other, attr)
#                    for attr in self.__slots__))

class ASaveStatusMove(AtomicMove):
    """
    Save game variables
    """
    __slots__ = ['status', 'flags']

    def __init__(self, status, flags):
        """
        __init__(flags) -> None

        Update the state of a game. Only needed for complex games in
        combination with undo.

        :Parameters:
          state : int
            Current game state.

          flags : int
            Update mode.
        """
        self.status = status
        self.flags = flags

    def redo(self, game):
        """
        redo(game) -> None

        Set game set.

        :Parameters:
          game : Game
            Current game object.
        """
        if (self.flags & 3) in (1, 3):
            game.set_status(self.status)

    def undo(self, game):
        """
        undo(game) -> None

        Restore game set.

        :Parameters:
          game : Game
            Current game object.
        """
        if (self.flags & 3) in (2, 3):
            game.set_status(self.status)

#    def cmpForRedo(self, other):
#        return (self.__class__ is other.__class__ and
#                all(getattr(self, attr) is getattr(other, attr)
#                    for attr in self.__slots__))

class AShuffleStackMove(AtomicMove):
    """
    Shuffle all cards of a stack. Saves the seed. Does not flip any cards.
    """
    __slots__ = ['stack_id', 'card_ids', 'state']

    def __init__(self, stack, random):
        """
        __init__(stack) -> None

        Shuffle cards of a pile saving the random seed.

        :Parameters:
          stack : Stack
            Pile with cards to shuffle.
          state : int
            Current random state.

        :IVariables:
          stack_id : Stack
            Id of pile with cards to shuffle.
          cards_ids : list(int...)
            Ids of cards in pile.
          state : int
            Current random state.
        """
        assert stack
        self.stack_id = stack.id
        self.card_ids = tuple([c._index for c in stack.cards])
        self.state = random.getstate()

    def redo(self, game):
        """
        redo(game) -> None

        Shuffle cards in the pile.

        :Parameters:
          game : Game
            Current game object.
        """
        stack = game.allstacks[self.stack_id]
        # paranoia
        assert stack is game.s['talon']
        # shuffle (see random)
        game.random.setstate(self.state)
        seq = stack.cards
        n = len(seq) - 1
        while n > 0:
            j = game.random.randint(0, n)
            seq[n], seq[j] = seq[j], seq[n]
            n = n - 1
        stack.refreshView()

    def undo(self, game):
        """
        undo(game) -> None

        Restore cards order on the pile.

        :Parameters:
          game : Game
            Current game object.
        """
        stack = game.allstacks[self.stack_id]
        # restore cards
        cards = []
        for id in self.card_ids:
            c = game.cards[id]
            assert c.id == id
            cards.append(c)
        stack.cards = cards
        # restore the state
        game.random.setstate(self.state)
        stack.refreshView()

#    def cmpForRedo(self, other):
#        return (self.__class__ is other.__class__ and
#                all(getattr(self, attr) is getattr(other, attr)
#                    for attr in self.__slots__))

class ASingleCardMove(AtomicMove):
    """
    Play the arbitrary card in position from_stack to build on to_stack.
    """
    __slots__ = ['from_stack_id', 'to_stack_id', 'from_pos', 'frames',
                 'shadow']

    def __init__(self, from_stack, to_stack, from_pos, frames=-1, shadow=-1):
        """
        __init__(from_stack, to_stack, position[, frames=-1, shadows=-1]) ->
          None

        :Parameters:
          from_stack : Stack
            Pile cards being played.
          to_stack : Stack
            Pile cards being built.
          position : int
            Arbitrary card position in pile to play.

        :Keywords:
          frames : int
            Number of animation frames. Default is 8 frames.
          shadows : int
            Draw shadow under cards being played. Default is 'shadow' option.

        :IVariables:
          from_pos : int
            Arbitrary card position in pile to play.
          from_stack_id : int
            Id of pile cards being played.
          to_stack_id : int
            Id of pile cards being built.
          frames : int
            Number of animation frames. Default is 8 frames.
          shadows : int
            Draw shadow under cards being played. Default is 'shadow' option.
        """
        assert from_stack is not None and to_stack is not None
        assert 0 <= from_pos < len(from_stack.cards)
        self.from_stack_id = from_stack.id
        self.to_stack_id = to_stack.id
        self.from_pos = from_pos
        self.frames = frames
        self.shadow = shadow

    def _doMove(self, game, from_stack, to_stack, redo=True):
        """
        _doMove(game, from_stack, to_stack[, redo=True]) -> None

        :Parameters:
          from_stack_id : int
            Id of pile with cards being played.
          to_stack_id : int
            Id of pile with cards being built.

        :Keywords:
          redo : bool
            Movement is not being undone.
        """
        pos = self.from_pos
        if game.move_state == game.S_PLAY:
            assert to_stack.cardsBuild(from_stack, [from_stack.cards[pos]])

        if redo:
            card = from_stack.removeCard(from_stack.cards[pos],
                                         update_positions=True)
            if self.frames != 0:
                x, y = to_stack.getPosition(card)
                animation.animate_play(game, from_stack, to_stack, [card], x,
                                    y, frames=self.frames, shadow=self.shadow)
            to_stack.addCard(card)
        else:
            card = from_stack.removeCard()
            to_stack.insertCard(card, pos)

    def redo(self, game):
        """
        redo(game) -> None

        Perform movement.

        :Parameters:
          game : Game
            Current game object.
        """
        from_stack = game.allstacks[self.from_stack_id],
        to_stack = game.allstacks[self.to_stack_id]
        self._doMove(game, from_stack, to_stack, redo=True)

    def undo(self, game):
        """
        undo(game) -> None

        Perform movement.

        :Parameters:
          game : Game
            Current game object.
        """
        from_stack = game.allstacks[self.to_stack_id],
        to_stack = game.allstacks[self.from_stack_id]
        self._doMove(game, to_stack, from_stack, redo=False)

#    def cmpForRedo(self, other):
#        return (self.__class__ is other.__class__ and
#                all(getattr(self, attr) is getattr(other, attr)
#                    for attr in self.__slots__))

class AInnerMove(AtomicMove):
    """
    Change position of single card within a stack.
    """
    __slots__ =['stack_id', 'from_stack_pos', 'to_stack_pos', 'frames',
                'shadow']

    def __init__(self, stack, from_pos, to_pos, frames=-1, shadow=-1):
        assert stack is not None
        ncards = len(stack.cards) - 1
        assert 0 <= from_pos <= ncards and 0 <= to_pos <= ncards
        self.stack_id = stack.id
        self.from_pos, self.to_pos = from_pos, to_pos
        self.frames = frames
        self.shadow = shadow

    def _doMove(self, game, stack, from_pos, to_pos):
        """
        _doMove(stack, from_pos, to_pos) -> None

        :Parameters:
          from_stack : Stack
            Pile cards being played.
          to_stack : Stack
            Pile cards being built.

        :Keywords:
          redo : bool
            Movement is not being undone.
        """
        card = stack.removeCard(stack.cards[from_pos], update_positions=True)
        stack.insertCard(card, to_pos)

    def redo(self, game):
        """
        redo(game) -> None

        Perform swap card movement from from_stack to to_stack.

        :Parameters:
          game : Game
            Current game object.
        """
        self._doMove(game.allstacks[self.stack_id], self.from_stack_pos,
                     self.to_stack_pos)

    def undo(self, game):
        """
        undo(game) -> None

        Perform swap card movement from to_stack to from_stack.

        :Parameters:
          game : Game
            Current game object.
        """
        self._doMove(game.allstacks[self.stack_id], self.to_stack_pos,
                     self.from_stack_pos)

#    def cmpForRedo(self, other):
#        return (self.__class__ is other.__class__ and
#                all(getattr(self, attr) is getattr(other, attr)
#                    for attr in self.__slots__))

class ASwapPairMove(AtomicMove):
    """
    Swap the selected top cards from a stack to another stack.
    """
    __slots__ =['from_stack_id', 'to_stack_id', 'frames', 'shadow']

    def __init__(self, from_stack, to_stack, frames=-1, shadow=-1):
        """
        __init__(from_stack, to_stack)
          -> None

        :Parameters:
          from_stack : Stack
            Pile cards being swap.

          to_stack : Stack
            Pile cards being swap.

        :IVariables:
          from_stack_id : int
            Id of pile cards being swapped.

          to_stack_id : int
            Id of pile cards being swapped.
        """
        assert from_stack is not to_stack
        assert from_stack is not None and to_stack is not None
        self.from_stack_id = from_stack.id
        self.to_stack_id = to_stack.id
        self.frames = frames
        self.shadow = shadow

    def _do_move(self, game, from_stack, to_stack):
        """
        _doMove(game, from_stack, to_stack) -> None

        Perform the actual movement.

        :Parameters:
          game : Game
            Current game object.

          from_stack : Stack
            Pile cards being swapped.

          to_stack : Stack
            Pile cards being swapped.
        """
        if game.move_state == game.S_PLAY:
            assert to_stack.cardsBuild(from_stack, from_stack.cards[-1:])
        # Pick up top card from_stack and store
        to_card = from_stack.TOPCARD
        from_stack.removeCard()
        # Pick up top card to_stack and store
        from_card = to_stack.TOPCARD
        to_stack.removeCard()
        from_stack.addCard(from_card)
        to_stack.addCard(to_card)
        # Update stack positions
        from_stack.updatePositions()
        to_stack.updatePositions()

    def redo(self, game):
        """
        redo(game) -> None

        Perform swap card movement from from_stack to to_stack.

        :Parameters:
          game : Game
            Current game object.
        """
        self._doMove(game, self.ncards, game.allstacks[self.from_stack_id],
                     game.allstacks[self.to_stack_id])

    def undo(self, game):
        """
        undo(game) -> None

        Perform swap card movement from to_stack to from_stack.

        :Parameters:
          game : Game
            Current game object.
        """
        self._doMove(game, self.ncards, game.allstacks[self.to_stack_id],
                     game.allstacks[self.from_stack_id])

#    def __cmp__(self, other):
#        return (self.__class__ is other.__class__ and
#                all(getattr(self, attr) is getattr(other, attr)
#                    for attr in self.__slots__))

class ADropPairMove(AtomicMove):
    """
    Swap the selected top cards from a stack to another stack.
    """
    __slots__ =['from_stack_id', 'to_stack_id']

    def __init__(self, from_stack, to_stack):
        """
        __init__(from_stack, to_stack)
          -> None

        :Parameters:
          from_stack : Stack
            Pile cards being swap.

          to_stack : Stack
            Pile cards being swap.

        :IVariables:
          from_stack_id : int
            Id of pile cards being swapped.

          to_stack_id : int
            Id of pile cards being swapped.
        """
        assert from_stack is not to_stack
        self.from_stack_id = from_stack.id
        self.to_stack_id = to_stack.id

    def _do_move(self, game, from_stack, to_stack, dropstack=None):
        """
        _doMove(game, from_stack, to_stack[, dropstack=Foundation[0]) -> None

        Perform the actual movement.

        :Parameters:
          game : Game
            Current game object.

          from_stack : Stack
            Pile cards being swapped.

          to_stack : Stack
            Pile cards being swapped.
        """
        assert from_stack is not to_stack
        dropstack = dropstack or game.s['foundations'][0]
        if game.move_state == game.S_PLAY:
            assert to_stack.cardsBuild(from_stack, from_stack.cards[-1:])
        from_cards = from_stack.cards[-1:]
        to_cards = to_stack.cards[-1:]
        if CONF_OPT['animate_flip']:
            from_stack.unshadeStack()
            coords = to_stack.getPosition()
            game.animate_play(from_stack, dropstack, from_cards, *coords)
            to_stack.unshadeStack()
            coords = to_stack.getPosition()
            game.animate_play(to_stack, dropstack, to_cards, *coords)
        cards = from_cards + to_cards
        from_stack.removeCard()
        to_stack.removeCard()
        for c in cards:
            dropstack.addCard(c)
        from_stack.updatePositions()
        to_stack.updatePositions()
        dropstack.updatePositions()

    def redo(self, game):
        """
        redo(game) -> None

        Perform drop card movement from from_stack and to_stack.

        :Parameters:
          game : Game
            Current game object.
        """
        self._doMove(game, self.ncards, game.allstacks[self.from_stack_id],
                     game.allstacks[self.to_stack_id])

    def undo(self, game):
        """
        undo(game) -> None

        Perform drop card movement from to_stack and from_stack.

        :Parameters:
          game : Game
            Current game object.
        """
        self._doMove(game, self.ncards, game.allstacks[self.to_stack_id],
                     game.allstacks[self.from_stack_id])

#    def __cmp__(self, other):
#        return (self.__class__ is other.__class__ and
#                all(getattr(self, attr) is getattr(other, attr)
#                    for attr in self.__slots__))